/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implementation of class char-stream, aka a input buffer.
 *
 * @author  WXQ#XDU
 * @date    2024.11
 */

// To reader:
// As a demo compiler, AMCC assumes that:
// 1. the input file is NOT too large, and
// 2. only once I/O system call can read the entire contents
//    of this file.    ^_^


#include <stdio.h>
#include <stdlib.h>

#include "_lex_inner.h"

#define CHAR_EOT    ((char)-1)   // temporary ENF-OF-TOKEN
#define BUFFER_EMPTY(pStream)   (NULL == (pStream) || CHAR_EOF == ((pStream)->pBuffer[0]))

static char * load_file_(t_charstream_ptr pThis, FILE * fp, const char *filePath);

t_charstream_ptr charstream_new(FILE * fp, const char* filePath) {
    t_charstream_ptr pThis = (t_charstream_ptr)calloc(1, sizeof(t_charstream));
    if (NULL == pThis) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }
    if (NULL == load_file_(pThis, fp, filePath)) {
        free (pThis);
        return NULL;
    }

    pThis->fpInput          = fp;
    pThis->filePath         = filePath;
    pThis->idxForward       = 0; // not getChar() yet
    pThis->idxBeginMarker   = 0;
    pThis->idxEndMarker     = -1;
    pThis->charNextToToken  = CHAR_EOT;
    return pThis;
}

void charstream_destroy(t_charstream_ptr pThis) {
    if (NULL != pThis) {
        free(pThis->pBuffer);
        free(pThis);
    }
}

// Recover end-marker created by markEnd()
static void clearEndMarker_(t_charstream_ptr pThis) {
    long idxEndMark = pThis->idxEndMarker;
    if (idxEndMark > -1 && CHAR_EOT != pThis->charNextToToken) {
        pThis->pBuffer[idxEndMark] = pThis->charNextToToken;
    }
    pThis->idxEndMarker    = -1;
    pThis->charNextToToken = CHAR_EOT;
}

// simulates getc() of C-stdio
char charstream_readChar(t_charstream_ptr pThis) {
    if ( BUFFER_EMPTY(pThis) )
        return CHAR_EOF;

    clearEndMarker_(pThis);
    char ch = CHAR_EOF;
    if ( pThis->idxForward < pThis->szBuffer ) // not overflow
        ch = pThis->pBuffer[pThis->idxForward];
    ++(pThis->idxForward);
    return ch;
}

// simulates ungetc() of C-stdio
bool charstream_backChar(t_charstream_ptr pThis) {
    if ( BUFFER_EMPTY(pThis) )
        return false;

    clearEndMarker_(pThis);
    --(pThis->idxForward);
    if (pThis->idxForward < 0) {  // underflow
        pThis->idxForward = 0; // Virtually: not getChar() yet
    }

    return pThis->idxForward > 0;
}

void charstream_markBegin(t_charstream_ptr pThis) {
    if ( BUFFER_EMPTY(pThis) )
        return ;

    clearEndMarker_(pThis);
    if (pThis->idxForward == 0) {  // not getChar() yet
        pThis->idxBeginMarker = 0;
        return;
    }

    if ( pThis->idxForward < pThis->szBuffer ) // not overflow
        pThis->idxBeginMarker = pThis->idxForward;
    else
        pThis->idxBeginMarker = pThis->szBuffer - 1;
}

void charstream_markEnd(t_charstream_ptr pThis) {
    if ( BUFFER_EMPTY(pThis) )
        return ;

    clearEndMarker_(pThis);
    if (pThis->idxForward == 0) {
        // No ending marker if caller has not getChar() yet
        return;
    }

    // next position immediately following the end of token text
    long idxEndMarker = pThis->idxForward;
    if ( pThis->idxForward >= pThis->szBuffer ) { // overflow
        idxEndMarker = pThis->szBuffer - 1;
    }

    // used for recoverEndMarker()
    pThis->charNextToToken = pThis->pBuffer[idxEndMarker];
    pThis->pBuffer[idxEndMarker] = '\0';
    pThis->idxEndMarker = idxEndMarker;
}

const char * charstream_markedText(t_charstream_ptr pThis) {
    if ( BUFFER_EMPTY(pThis) )
        return NULL == pThis ? NULL : pThis->pBuffer;
    else
        return pThis->pBuffer + pThis->idxBeginMarker;
}

int charstream_markedText_length(t_charstream_ptr pThis) {
    if ( BUFFER_EMPTY(pThis) )
        return 0;

    return pThis->idxEndMarker - pThis->idxBeginMarker;
}


// Load file content into memory.
static char * load_file_(t_charstream_ptr pThis,
                         FILE * fp,
                         const char *filePath)
{
    pThis->szBuffer = 0;
    fseek(fp, 0, SEEK_END);
    long fileLen = ftell(fp);
    if (fileLen < 0) {
        ERROR_REPORT_FILEOP(EC_READFILE, filePath);
        return NULL;
    }

    // extra one byte for CHAR_EOF
    char * pSpace = (char*)malloc(++fileLen);
    if (NULL == pSpace) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }
    pThis->pBuffer  = pSpace;
    pThis->szBuffer = fileLen--;
    if (fileLen == 0) {
        pSpace[0] = CHAR_EOF;
        return pSpace;
    }

    fseek(fp, 0, SEEK_SET);
    size_t  szRead = fread(pSpace, 1, fileLen, fp);
    if ( szRead != fileLen && ferror(fp) ) {
        // treats the file as empty on read error
        pSpace[0] = CHAR_EOF;
        ERROR_REPORT_FILEOP(EC_READFILE, filePath);
    } else {
        pSpace[szRead] = CHAR_EOF;
        pSpace[fileLen] = CHAR_EOF;
    }

    return pSpace;
}
